﻿using System;
using System.IO;
using System.Collections.Generic;
using CalcSharp.Core.Containers;

namespace CalcSharp.Core
{
    public enum BMPType : short
    {
        Windows = 0x424d,           //BM
        OS2BitmapArray = 0x4241,    //BA
        OS2ColorIcon = 0x4349,      //CI
        OS2ColorPointer = 0x4350,   //CP
        OS2Icon = 0x4943,           //CI
        OS2Pointer = 0x5054         //PT
    }

    public class BMPDriver
    {

        private Stream Input;

        public BMPType bfType;
        private uint bfSize;
        private ushort bfReserved1, bfReserved2;
        private uint bfOffBits;

        //-----------------------------------

        private uint biSize;
        [CLSCompliant(false)]
        public uint biWidth, biHeight;
        private ushort biPlanes, biBitCount;
        private uint biCompression, biSizeImage, biClrUsed, biClrImportant;
        private int biXPelsPerMeter, biYPelsPerMeter;

        public BMPDriver(Stream inp)
        {
            this.Input = inp;
            this.bfType = BMPType.Windows;
            this.bfOffBits = 1078;
            this.biSize = 40;
            this.biPlanes = 1;
        }

        private static uint calculatepadding(uint start)
        {
            uint i = start - 1;
            do
            {
                ++i;
            }
            while (i % 4 != 0);
            return i - start;
        }

        private void ReadHeader()
        {
            if (this.Input.Length > 53)
            {
                BinaryReader Br = new BinaryReader(this.Input);
                byte[] data = Br.ReadBytes(54);
                Br = null;
                this.bfType = (BMPType)BitConverter.ToUInt16(data, 0);
                this.bfSize = BitConverter.ToUInt32(data, 2);
                this.bfReserved1 = BitConverter.ToUInt16(data, 6);
                this.bfReserved2 = BitConverter.ToUInt16(data, 8);
                this.bfOffBits = BitConverter.ToUInt32(data, 10);
                //-------------------------------------------------------
                this.biSize = BitConverter.ToUInt32(data, 14);
                this.biWidth = BitConverter.ToUInt32(data, 18);
                this.biHeight = BitConverter.ToUInt32(data, 22);
                this.biPlanes = BitConverter.ToUInt16(data, 26);
                this.biBitCount = BitConverter.ToUInt16(data, 28);
                this.biCompression = BitConverter.ToUInt32(data, 30);
                this.biSizeImage = BitConverter.ToUInt32(data, 34);
                this.biXPelsPerMeter = BitConverter.ToInt32(data, 38);
                this.biYPelsPerMeter = BitConverter.ToInt32(data, 42);
                this.biClrUsed = BitConverter.ToUInt32(data, 46);
                this.biClrImportant = BitConverter.ToUInt32(data, 50);
            }
            else throw new NotSupportedException("File is too small to read in headers");
        }

        private void WriteHeader()
        {
            List<byte> data = new List<byte>();
            byte[] tmp;
            tmp = BitConverter.GetBytes((short)this.bfType);
            Array.Reverse(tmp);
            data.AddRange(tmp);
            data.AddRange(BitConverter.GetBytes(this.bfSize));
            data.AddRange(BitConverter.GetBytes(this.bfReserved1));
            data.AddRange(BitConverter.GetBytes(this.bfReserved2));
            data.AddRange(BitConverter.GetBytes(this.bfOffBits));
            //-------------------------------------------------------
            data.AddRange(BitConverter.GetBytes(this.biSize));
            data.AddRange(BitConverter.GetBytes(this.biWidth));
            data.AddRange(BitConverter.GetBytes(this.biHeight));
            data.AddRange(BitConverter.GetBytes(this.biPlanes));
            data.AddRange(BitConverter.GetBytes(this.biBitCount));
            data.AddRange(BitConverter.GetBytes(this.biCompression));
            data.AddRange(BitConverter.GetBytes(this.biSizeImage));
            data.AddRange(BitConverter.GetBytes(this.biXPelsPerMeter));
            data.AddRange(BitConverter.GetBytes(this.biYPelsPerMeter));
            data.AddRange(BitConverter.GetBytes(this.biClrUsed));
            data.AddRange(BitConverter.GetBytes(this.biClrImportant));
            BinaryWriter bw = new BinaryWriter(this.Input);
            bw.Write(data.ToArray());
            bw = null;
        }

        public AMatrix ReadBMPToMatrix(out long count, MtrxStorageType t)
        {
            AMatrix ret;
            this.ReadHeader();
            if (this.biBitCount != 8 && this.biBitCount != 16 && this.biBitCount != 24 && this.biBitCount != 32)
            {
                throw new NotSupportedException("8, 16, 24 and 32 bit images supported only");
            }
            switch (t)
            {
                case MtrxStorageType.tdouble:
                    ret = new DoubleMatrix((int)this.biHeight, (int)this.biWidth);
                    break;
                case MtrxStorageType.tfloat:
                    ret = new FloatMatrix((int)this.biHeight, (int)this.biWidth);
                    break;
                case MtrxStorageType.tint24:
                    ret = new Int24Matrix((int)this.biHeight, (int)this.biWidth);
                    break;
                default:
                    ret = new FloatMatrix((int)this.biHeight, (int)this.biWidth);
                    break;
            }
            this.Input.Seek(0, SeekOrigin.Begin);
            this.Input.Seek(54, SeekOrigin.Begin);
            BinaryReader br = new BinaryReader(this.Input);
            byte[] data;
            bool paddneeded = false;
            uint padd = 0;
            if ((this.biWidth * (this.biBitCount / 8)) % 4 != 0)
            {
                paddneeded = true;
                padd = calculatepadding((uint)(this.biWidth * (this.biBitCount / 8)));
            }

            Int24 cnv = new Int24();

            for (int i = (int)(this.biHeight-1); i >= 0; i--)
            {
                for (int j = 0; j < this.biWidth; j++)
                {
                    data = br.ReadBytes(this.biBitCount / 8);
                    cnv.FromByteArray(data);
                    ret[i, j] = Convert.ToDouble(cnv.ToInt32());
                }
                if (paddneeded) br.ReadBytes((int)padd); //minden sor végén 2byte hulladék
            }
            br.Close();
            this.Input.Close();
            count = this.biHeight * this.biWidth * (byte)t;
            return ret;
        }

        public void SaveMatrixToBMP(AMatrix Matrix)
        {
            this.bfType = BMPType.Windows;
            bool paddneeded = false;
            byte[] padding = null;
            this.biWidth = (uint)Matrix.Columns;
            this.biHeight = (uint)Matrix.Rows;
            this.biBitCount = 24;
            if ((this.biWidth * (this.biBitCount / 8)) % 4 != 0)
            {
                paddneeded = true;
                padding = new byte[calculatepadding((uint)(this.biWidth * (this.biBitCount / 8)))];
            }

            if (!paddneeded) this.bfSize = 54 + (uint)(Matrix.Rows * Matrix.Columns);
            else this.bfSize = 54 + (uint)(Matrix.Rows * Matrix.Columns) + (uint)(Matrix.Rows+1 * padding.Length);
            this.bfOffBits = 54;
            this.biSize = 40;
            this.biPlanes = 1;
            this.biCompression = 0;
            this.biSizeImage = (uint)(Matrix.Rows * Matrix.Columns) + (uint)(Matrix.Rows * 2);
            this.biClrImportant = 0;
            this.biClrUsed = 0;
            this.biXPelsPerMeter = 0x0B130000;
            this.biYPelsPerMeter = 0x0B130000;
            this.WriteHeader();

            Int24 cnv = new Int24();
            BinaryWriter bw = new BinaryWriter(this.Input);
            byte[] tmp;

            for (int i = (Matrix.Rows-1); i >= 0; i--)
            {
                for (int j = 0; j < Matrix.Columns; j++)
                {
                    cnv.FromInt32(Convert.ToInt32(Matrix[i, j]));
                    tmp = cnv.ToByteArray();
                    Array.Reverse(tmp);
                    bw.Write(tmp);
                }
                if (paddneeded) bw.Write(padding);
            }
            bw.Close();
            this.Input.Close();
        }
    }
}